import { Clock, Vector3, Quaternion, Matrix4 } from 'three'
import * as THREE from 'three'
// const RAPIER_PATH = 'https://cdn.skypack.dev/@dimforge/rapier3d-compat@0.12.0'

const frameRate = 60

const clock = new Clock()
const _scale = new Vector3(1, 1, 1)
const ZERO = new Vector3()

let RAPIER = null

function getShape(mesh) {
    const { geometry, matrixWorld } = mesh

    // console.log('🚀 ~ getShape ~ po:', threeScene.tempCenter)
    const { x, y, z } = threeScene.tempCenter
    console.log('🚀 ~ getShape ~  x, y, z :', x, y, z)

    // const m = matrixWorld.elements
    // const [x, y, z] = [m[12], m[13], m[14]]
    // console.log('🚀 ~ getShape ~ x, y, z:', x, y, z)

    const parameters = geometry.parameters

    if (geometry.type === 'BoxGeometry') {
        const sx = parameters.width !== undefined ? parameters.width / 2 : 0.5
        const sy = parameters.height !== undefined ? parameters.height / 2 : 0.5
        const sz = parameters.depth !== undefined ? parameters.depth / 2 : 0.5

        return RAPIER.ColliderDesc.cuboid(sx, sy, sz)
    } else if (
        geometry.type === 'SphereGeometry' ||
        geometry.type === 'IcosahedronGeometry'
    ) {
        const radius = parameters.radius !== undefined ? parameters.radius : 1
        return RAPIER.ColliderDesc.ball(radius)
    } else if (geometry.type === 'PlaneGeometry') {
        const radius = parameters.radius !== undefined ? parameters.radius : 1
        const height = parameters.height !== undefined ? parameters.height : 1
        // const segments =
        //     parameters.radialSegments !== undefined
        //         ? parameters.radialSegments
        //         : 8
        const segment = height / 2 - radius
        return RAPIER.ColliderDesc.capsule(segment, radius)
    } else if (geometry.type === 'BufferGeometry') {
        const vertices = []
        const vertex = new Vector3()
        const position = geometry.getAttribute('position')
        // const { x, y, z } = {
        //     x: -51.3186,
        //     y: 2.04156137,
        //     z: 16.192337000000002,
        // }

        for (let i = 0; i < position.count; i++) {
            vertex.fromBufferAttribute(position, i)
            vertices.push(vertex.x - x, vertex.y - y, vertex.z - z)
            // vertices.push(vertex.x , vertex.y , vertex.z )
        }

        // if the buffer is non-indexed, generate an index buffer
        const indices =
            geometry.getIndex() === null
                ? Uint32Array.from(Array(parseInt(vertices.length / 3)).keys())
                : geometry.getIndex().array

        return RAPIER.ColliderDesc.trimesh(vertices, indices)
    }

    return null
}

// function getShape(geometry) {
//     const parameters = geometry.parameters

//     // TODO change type to is*
//     if(geometry.type !== 'BufferGeometry'){
//         console.log('----')
//     }

//     if (geometry.type === 'BoxGeometry') {
//         const sx = parameters.width !== undefined ? parameters.width / 2 : 0.5
//         const sy = parameters.height !== undefined ? parameters.height / 2 : 0.5
//         const sz = parameters.depth !== undefined ? parameters.depth / 2 : 0.5

//         return RAPIER.ColliderDesc.cuboid(sx, sy, sz)
//     } else if (
//         geometry.type === 'SphereGeometry' ||
//         geometry.type === 'IcosahedronGeometry'
//     ) {
//         const radius = parameters.radius !== undefined ? parameters.radius : 1
//         return RAPIER.ColliderDesc.ball(radius)
//     } else if (geometry.type === 'PlaneGeometry') {
//         const radius = parameters.radius !== undefined ? parameters.radius : 1
//         const height = parameters.height !== undefined ? parameters.height : 1
//         // const segments =
//         //     parameters.radialSegments !== undefined
//         //         ? parameters.radialSegments
//         //         : 8
//         const segment = height / 2 - radius
//         return RAPIER.ColliderDesc.capsule(segment, radius)
//     } else if (geometry.type === 'BufferGeometry') {
//         const vertices = []
//         const vertex = new Vector3()
//         const position = geometry.getAttribute('position')

//         for (let i = 0; i < position.count; i++) {
//             vertex.fromBufferAttribute(position, i)
//             vertices.push(vertex.x, vertex.y, vertex.z)
//         }

//         // if the buffer is non-indexed, generate an index buffer
//         const indices =
//             geometry.getIndex() === null
//                 ? Uint32Array.from(Array(parseInt(vertices.length / 3)).keys())
//                 : geometry.getIndex().array

//         return RAPIER.ColliderDesc.trimesh(vertices, indices)
//     }

//     return null
// }

async function RapierPhysics() {
    RAPIER = await import('@dimforge/rapier3d')

    // Docs: https://rapier.rs/docs/api/javascript/JavaScript3D/

    const gravity = new Vector3(0.0, -9.81, 0.0)
    const world = new RAPIER.World(gravity)
    world.clock = clock

    const meshes = []
    const meshMap = new WeakMap()

    const _vector = new Vector3()
    const _quaternion = new Quaternion()
    const _matrix = new Matrix4()

    function addScene(scene) {
        // console.log('🚀 ~ addScene ~ scene:', scene)
        scene.traverse(function (child) {
            if (child.isMesh) {
                const physics = child.userData.physics

                // console.log('🚀 ~ child:', child)
                if (physics) {
                    addMesh(child, physics.mass, physics.restitution)
                }
            }
        })
    }

    function addMesh(mesh, mass = 0, restitution = 0) {
        // console.log('🚀 ~ addMesh ~ mesh:', mesh)
        const shape = getShape(mesh)

        if (shape === null) return

        shape.setMass(mass)
        shape.setRestitution(restitution)

        const body = mesh.isInstancedMesh
            ? createInstancedBody(mesh, mass, shape)
            : createBody(mesh.position, mesh.quaternion, mass, shape)

        if (mass > 0) {
            meshes.push(mesh)
            meshMap.set(mesh, body)
        }
    }

    // function addMesh(mesh, mass = 0, restitution = 0) {
    //     console.log("🚀 ~ addMesh ~ mesh:", mesh)
    //     const shape = getShape(mesh.geometry)

    //     if (shape === null) return

    //     shape.setMass(mass)
    //     shape.setRestitution(restitution)

    //     const body = mesh.isInstancedMesh
    //         ? createInstancedBody(mesh, mass, shape)
    //         : createBody(mesh.position, mesh.quaternion, mass, shape)

    //     if (mass > 0) {
    //         meshes.push(mesh)
    //         meshMap.set(mesh, body)
    //     }
    // }

    function createInstancedBody(mesh, mass, shape) {
        const array = mesh.instanceMatrix.array

        const bodies = []

        for (let i = 0; i < mesh.count; i++) {
            const position = _vector.fromArray(array, i * 16 + 12)
            bodies.push(createBody(position, null, mass, shape))
        }

        return bodies
    }

    function createBody(position, quaternion, mass, shape) {
        // console.log("ok :", position, quaternion, mass, shape)
        const desc =
            mass > 0
                ? RAPIER.RigidBodyDesc.dynamic()
                : RAPIER.RigidBodyDesc.fixed()
        desc.setTranslation(...position)
        if (quaternion !== null) desc.setRotation(quaternion)

        const body = world.createRigidBody(desc)

        const collider = world.createCollider(shape, body)

        body.co = collider

        return body
    }

    function setMeshPosition(mesh, position, index = 0) {
        let body = meshMap.get(mesh)

        if (mesh.isInstancedMesh) {
            body = body[index]
        }

        body.setAngvel(ZERO)
        body.setLinvel(ZERO)
        body.setTranslation(position)
    }

    function setMeshVelocity(mesh, velocity, index = 0) {
        let body = meshMap.get(mesh)

        if (mesh.isInstancedMesh) {
            body = body[index]
        }

        body.setLinvel(velocity)
    }

    //

    let i = 0
    function step() {
        world.timestep = clock.getDelta()

        world.step()

        for (let i = 0, l = meshes.length; i < l; i++) {
            const mesh = meshes[i]

            if (mesh.isInstancedMesh) {
                const array = mesh.instanceMatrix.array
                const bodies = meshMap.get(mesh)

                for (let j = 0; j < bodies.length; j++) {
                    const body = bodies[j]

                    const position = body.translation()
                    _quaternion.copy(body.rotation())

                    _matrix
                        .compose(position, _quaternion, _scale)
                        .toArray(array, j * 16)
                }

                mesh.instanceMatrix.needsUpdate = true
                mesh.computeBoundingSphere()
            } else {
                const body = meshMap.get(mesh)
                mesh.position.copy(body.translation())
                mesh.quaternion.copy(body.rotation())
            }
        }
    }

    // animate

    // setInterval(step, 1000 / frameRate)

    return {
        addScene: addScene,
        addMesh: addMesh,
        setMeshPosition: setMeshPosition,
        setMeshVelocity: setMeshVelocity,
        step: step,
        meshMap,
        RAPIER,
        world,
    }
}

export { RapierPhysics }

let idx = 0
export class MyCharacterController {
    constructor(RAPIER, world) {
        this.RAPIER = RAPIER
        this.world = world

        this.gravity = new Vector3(0.0, -9.81, 0.0)
        this.gravityVelocity = new Vector3(0.0, 0.0, 0.0)
        this.rigidBody = this.createRigid({ x: 1, y: 0.99, z: 1 })
        this.collider = this.createCollider(this.rigidBody, {
            x: 0,
            y: 0,
            z: 0,
        })
        this.characterController = this.createCharacterController()
        this.nextTranslation = new Vector3()
    }

    createRigid(translation) {
        const { RAPIER, world } = this

        const rigidBodyDesc =
            RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(
                translation.x,
                translation.y,
                translation.z
            )
        // .setTranslation(translation)
        // .setCcdEnabled(true)
        // .setSoftCcdPrediction(0.5)
        // .setGravityScale(1)

        // All done, actually build the rigid-body.
        const rigidBody = world.createRigidBody(rigidBodyDesc)
        // rigidBody.setGravityScale(0)
        // The integer handle of the rigid-body can be read from the `handle` field.
        // const rigidBodyHandle = rigidBody.handle

        return rigidBody
    }

    createCollider(rigidBody, translation = { x: 0, y: 0, z: 0 }) {
        const { RAPIER, world } = this
        const colliderDesc = RAPIER.ColliderDesc.capsule(0.7, 0.2)
            // The collider translation wrt. the body it is attached to.
            // Default: the zero vector.
            .setTranslation(translation.x, translation.y, translation.z)
        // The collider rotation wrt. the body it is attached to, as a unit quaternion.
        // Default: the identity rotation.
        // .setRotation({ w: 1.0, x: 0.0, y: 0.0, z: 0.0 })
        // The collider density. If non-zero the collider's mass and angular inertia will be added
        // to the inertial properties of the body it is attached to.
        // Default: 1.0
        // .setDensity(1.3)
        // The friction coefficient of this collider.
        // Default: 0.5
        // .setFriction(0.0)
        // Whether this collider is a sensor.
        // Default: false
        // .setSensor(true)

        // Or create the collider and attach it to a rigid-body.
        const collider = world.createCollider(colliderDesc, rigidBody)
        return collider
    }

    createCharacterController() {
        // Create character controller from world
        const controller = this.world.createCharacterController(0.01) // Spacing

        // Update controller settings
        controller.setSlideEnabled(true) // Allow sliding down hill
        controller.setMaxSlopeClimbAngle((75 * Math.PI) / 180) // Don’t allow climbing slopes larger than 45 degrees.
        controller.setMinSlopeSlideAngle((45 * Math.PI) / 180) // Automatically slide down on slopes smaller than 30 degrees.
        controller.enableAutostep(0.5, 0.2, true) // (maxHeight, minWidth, includeDynamicBodies) Stair behavior
        // controller.enableSnapToGround(0.5) // (distance) Set ground snap behavior
        // controller.setApplyImpulsesToDynamicBodies(true) // Add push behavior
        controller.setCharacterMass(65) // (mass) Set character mass
        return controller
    }

    testCollision(desiredTranslation, callback) {
        this.world.timestep = this.world.clock.getDelta()
        this.world.step()

        const { characterController, collider, rigidBody } = this
        // console.log('刚体类型:', rigidBody.bodyType())
        characterController.computeColliderMovement(
            // this.rigidBody.collider(0),
            collider, // The collider we would like to move.
            desiredTranslation // The movement we would like to apply if there wasn’t any obstacle.
        )
        // console.log('y', desiredTranslation)

        const translation = this.rigidBody.translation()
        // this.getDistanceToGround(translation)
        const computedMovement = this.characterController.computedMovement()
        // console.log(
        //     '🚀 ~ testCollision ~ computedMovement:',
        //     computedMovement.y
        // )
        // computedMovement.y = 0

        // console.log(
        //     'computedMovement',
        //     computedMovement.x,
        //     computedMovement.y,
        //     computedMovement.z
        // )

        // translation.y 从 1.9 到 0.5
        this.nextTranslation.copy(translation)
        this.nextTranslation.add(computedMovement)
        this.rigidBody.setNextKinematicTranslation(this.nextTranslation)

        // this.world.step()
        callback(this.nextTranslation)

        for (let i = 0; i < characterController.numComputedCollisions(); i++) {
            let collision = characterController.computedCollision(i)
            // console.log('ok', collision)

            // Do something with that collision information.
        }
    }
}
